package cn.blnp.net.flowable.ext.child.transaction;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ExecutionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>子流程-事务子流程</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/20 11:40
 */
@Slf4j
@SpringBootTest
public class TransactionChildFlowTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ManagementService managementService;

    /**
     *  <h2>事务子流程</h2>
     *  <hr/>
     *  <p>
     *   &emsp;&emsp;事务子流程也称作事务块，是一种特殊的内嵌子流程，用于处理一组必须在同一个事务中完成的活动，使它们共同成功或失败。事务子流程中如果有一个活动失败或者取消，
     *   那么整个事务子流程的所有活动都会回滚，可能导致以下3种不同的结果。
     *   <ol>
     *       <li>
     *           <b>事务成功：</b>没有取消也没有因为异常终止。如果事务子流程是成功的，就会使用外出顺序流继续执行。如果流程后来抛出了一个补偿事件，成功的事务可能被补偿。需要注意的是，
     *           与普通内嵌子流程一样，事务可能在成功后，使用中间补偿事件进行补偿。
     *       </li>
     *       <li>
     *           <b>事务取消：</b>流程流转到取消结束事件。在这种情况下，所有的执行都被终止并被删除，然后剩余的一个执行被设置为取消边界事件，这会触发补偿。在补偿完成之后，事务子流程会
     *           沿着取消边界事务的外出顺序流向下流转。
     *       </li>
     *       <li>事务异常：</li>如果有错误事件被抛出，并且没有在事务子流程中被捕获，事务将异常结束（在事务子流程的边界上捕获错误也适用于这种情况）。在这种情况下，不会执行补偿。
     *   </ol>
     *  </p>
     *  <p>
     *   &emsp;&emsp;这里说的事务指BPMN事务，它与数据库ACID事务有相似之处，但它们是两个不同的概念，有以下几点不同。
     *   <ol>
     *       <li>
     *           数据库ACID事务一般持续时间很短，但BPMN事务可能持续较长时间（几小时，几天，甚至更长时间），如一个BPMN事务中包含用户任务，由于办理人的原
     * 因导致时间过长，或者BPMN事务可能会等待某个事件发生才往下执行。这样的操作通常要比在数据库中更新记录或使用事务队列存储消息花费更长的时间。
     *       </li>
     *       <li>
     *           BPMN事务一般会跨越多个ACID事务，因为通常无法在事务子流程中多个节点的流转过程中保持ACID事务。当BPMN事务处理涉及多个ACID事务时，会失去ACID特性。
     *       </li>
     *       <li>
     *           BPMN事务不能使用通常的方式回滚。这是因为BPMN事务可横跨多个ACID事务，在BPMN事务取消时部分ACID事务可能已经提交。
     *       </li>
     *       <li>
     *           BPMN事务通常需要长时间运行，所以缺乏隔离性和回滚机制，需要以不同的方式处理。在实现BPMN事务时，针对回滚机制的缺乏，可以使用补偿执行回
     * 滚。如果在事务范围内引发取消事件，则所有成功执行并具有补偿处理器的活动将执行补偿。针对隔离性的缺乏，通常需要使用特定领域的解决方法来解决。
     *       </li>
     *       <li>
     *           BPMN事务可以保证一致性，即所有活动都能够成功执行，或者某些活动无法执行时由所有已执行成功的活动补偿。无论哪种情况，BPMN事务最终都处于一
     * 致状态。但是，需要注意的是，在Flowable中，BPMN事务的一致性模型建立在流程执行的一致性模型之上。Flowable执行流程是ACID事务性的，并使用乐观
     * 锁解决并发问题。在Flowable中，BPMN的错误、取消和补偿事件同样建立在ACID事务和乐观锁的基础上。例如，当两个并发执行流转到取消结束事件时，可
     * 能会触发两次补偿，最终因为乐观锁冲突而失败。
     *       </li>
     *   </ol>
     *  </p>
     *  <h3>案例演示</h3>
     *  <p>
     *   &emsp;&emsp;该流程为电商用户下单支付流程。用户提交订单后，流程流转到“订单支付事务子流程”，完成订单支付需要经过“锁定库存”“用户支付订单”“扣减库存”3个节点。如果用户取消订单、
     *   10分钟未支付订单，或扣减库存失败，都会使子流程流转到取消结束事件并执行“释放库存”和“费用退回”补偿，被依附在事务子流程上的取消边界事件捕获后结束子流程。流程流转到“自动取消订单”
     *   服务节点，最终流转到结束节点。
     *      <b>流程定义文件：</b>{@link flow/transactionSubProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/20/682c1eee770d8.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("事务子流程-演示案例")
                .addClasspathResource("flow/transactionSubProcess.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //设置流程变量
        Map<String, Object> varMap1 = MapUtil.builder(new HashMap<String, Object>())
                .put("goodsNum", 100)
                .put("orderCost", 1000)
                .build();
        //启动流程实例
        ProcessInstance mainProcessInstance = runtimeService.startProcessInstanceById(procDef.getId(), varMap1);

        ExecutionQuery executionQuery = runtimeService.createExecutionQuery()
                .processInstanceId(mainProcessInstance.getId());
        //查询执行实例
        List<Execution> executionList1 = executionQuery.list();
        log.info("主流程发起后，执行实例数为{}，分别为：{}", executionList1.size(),
                JSON.toJSONString(executionList1, executionFilter));
        //查询主流程的流程变量
        Map<String, Object> mainProcessVarMap1 = runtimeService
                .getVariables(mainProcessInstance.getId());
        log.info("主流程的流程变量为：{}", mainProcessVarMap1);
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(mainProcessInstance.getId());
        //查询主流程第一个任务
        Task firstTaskOfMainProcess = taskQuery.singleResult();
        log.info("主流程发起后，流程当前所在用户任务为：{}", firstTaskOfMainProcess.getName());
        //完成主流程第一个任务
        log.info("办理用户任务：{}，启动事务子流程", firstTaskOfMainProcess.getName());
        taskService.complete(firstTaskOfMainProcess.getId());
        //查询执行实例
        List<Execution> executionList2 = executionQuery.list();
        log.info("子流程发起后，执行实例数为{}，分别为：{}", executionList2.size(),
                JSON.toJSONString(executionList2, executionFilter));
        //查询子流程第一个任务
        Task firstTaskOfSubProcess = taskService.createTaskQuery()
                .taskName("用户支付订单").processInstanceId(mainProcessInstance.getId())
                .includeProcessVariables().singleResult();
        log.info("子流程发起后，流程当前所在用户任务为：{}", firstTaskOfSubProcess.getName());
        //查询子流程的流程变量
        Map<String, Object> subProcessVarMap1 = firstTaskOfSubProcess
                .getProcessVariables();
        log.info("子流程的流程变量为：{}", subProcessVarMap1);
        log.info("办理用户任务：{}", firstTaskOfSubProcess.getName());
        //完成子流程第一个任务，后续到达服务节点
        taskService.complete(firstTaskOfSubProcess.getId());
        //查询执行实例
        List<Execution> executionList4 = executionQuery.list();
        log.info("主流程结束后，执行实例数为{}，执行实例信息为：{}", executionList4.size(),
                JSON.toJSONString(executionList4, executionFilter));

        /**
         *  演示结果：
         *  2025-05-20 14:28:08.801  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 部署流程定义：98283cf3-3543-11f0-8e49-50ebf6ec8f6d
         * 2025-05-20 14:28:08.806  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 流程定义编号：9893d006-3543-11f0-8e49-50ebf6ec8f6d
         * 2025-05-20 14:28:09.474  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 主流程发起后，执行实例数为2，分别为：[{"id":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":true},{"activityId":"firstUserTaskOfMainProcess","id":"98a13d8a-3543-11f0-8e49-50ebf6ec8f6d","parentId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":false}]
         * 2025-05-20 14:28:09.502  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 主流程的流程变量为：{orderCost=1000, goodsNum=100}
         * 2025-05-20 14:28:09.519  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 主流程发起后，流程当前所在用户任务为：用户提交订单
         * 2025-05-20 14:28:09.519  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 办理用户任务：用户提交订单，启动事务子流程
         * 2025-05-20 14:28:09.549  INFO 40568 --- [           main] c.b.n.f.e.c.t.TreasuryLockService        : 锁定库存完成！
         * 2025-05-20 14:28:09.668  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 子流程发起后，执行实例数为6，分别为：[{"id":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":true},{"activityId":"transactionSubProcess","id":"990ecc70-3543-11f0-8e49-50ebf6ec8f6d","parentId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":true},{"activityId":"boundaryEvent3","id":"990ef382-3543-11f0-8e49-50ebf6ec8f6d","parentId":"990ecc70-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":false},{"activityId":"firstUserTaskOfSubProcess","id":"990ef384-3543-11f0-8e49-50ebf6ec8f6d","parentId":"990ecc70-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":false},{"activityId":"secondUserTaskOfSubProcess","id":"990f1a98-3543-11f0-8e49-50ebf6ec8f6d","parentId":"990ecc70-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":false},{"activityId":"boundaryEvent1","id":"990fddf2-3543-11f0-8e49-50ebf6ec8f6d","parentId":"990ef384-3543-11f0-8e49-50ebf6ec8f6d","processInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","rootProcessInstanceId":"98a0a147-3543-11f0-8e49-50ebf6ec8f6d","scope":false}]
         * 2025-05-20 14:28:09.674  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 子流程发起后，流程当前所在用户任务为：用户支付订单
         * 2025-05-20 14:28:09.674  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 子流程的流程变量为：{orderCost=1000, goodsNum=100}
         * 2025-05-20 14:28:09.674  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 办理用户任务：用户支付订单
         * 2025-05-20 14:28:09.695 ERROR 40568 --- [           main] c.b.n.f.e.c.t.TreasuryDeductService      : 库存不足，订单将取消！
         * 2025-05-20 14:28:09.717  INFO 40568 --- [           main] c.b.n.f.e.c.t.RefundPaymentService       : 执行补偿，费用退回完成！
         * 2025-05-20 14:28:09.717  INFO 40568 --- [           main] c.b.n.f.e.c.t.TreasuryReleaseService     : 执行补偿，释放库存完成！
         * 2025-05-20 14:28:09.719  INFO 40568 --- [           main] c.b.n.f.e.c.t.CancelOrderService         : 自动取消订单完成！
         * 2025-05-20 14:28:09.801  INFO 40568 --- [           main] c.b.n.f.e.c.t.TransactionChildFlowTest   : 主流程结束后，执行实例数为0，执行实例信息为：[]
         **/
    }

    SimplePropertyPreFilter executionFilter = new SimplePropertyPreFilter(Execution.class,
            "id", "parentId", "scope", "businessKey", "processInstanceId",
            "rootProcessInstanceId", "activityId");
}
